import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';

import Ol_Interaction_Select from 'ol/interaction/select';
import Ol_Events_Condition from 'ol/events/condition';
import Style from 'ol/style/style';
import StyleCircle from 'ol/style/circle';
import StyleFill from 'ol/style/fill';
import StyleStroke from 'ol/style/stroke';

import { Feature, CustomLayer, DefaultStyle } from '../models';

import * as fromFeatureAction from '../store/actions/feature.action';
import * as fromFeatureSelector from '../store/selectors/feature.selector';
import * as fromSelectSelector from '../store/selectors/interaction-select.selector';
import { State } from '../../store';
import { StyleOptions, SimStyle } from '../class';
import { LayerTypes } from '../enums';
import { WFSOptions } from '../models/layer.model';

@Injectable()
export class InteractionSelectService {

  private interaction: any;
  private map: any;
  private layers: { [key: string]: CustomLayer } = {};
  private selected: any[] = [];
  private selectedFeature: Feature;
  private active: boolean = true;
  private styles: any[] = [];

  constructor(
    private store: Store<State>
  ) { 
    this.stylesBuilder();
    this.interactionBuilder(this.serializeLayers(this.layers));
    this.store.select(fromSelectSelector.getInteractionSelectActive)
      .subscribe((active: boolean) => {
        this.active = active;
        this.interaction && this.interaction.setActive(active);
      });
    this.store.select(fromFeatureSelector.getFeatureSelected).subscribe((selected: Feature) => {
      if(!selected) {
        this.selected = [];
        this.selectedLisener(this.selected[0]);
        this.interactionBuilder(this.serializeLayers(this.layers));
      }
    });
  }

  interactionBuilder(layers: any[]): void {
    if(!this.map) return;
    this.interaction && this.map.removeInteraction(this.interaction);
    this.interaction = new Ol_Interaction_Select({
      layers: layers,
      toggleCondition: Ol_Events_Condition.click,
      multi: false,
      features: this.selected,
      style: (feature) => {
        const layerId = feature.get('LAYER_ID');
        const layer: CustomLayer = this.layers[layerId];
        if(layer && layer.styleActive) {
          return (new SimStyle(layer.styleActive)).getStyleAsOl();
        } else {
          return this.getStyle(feature, layer.getColorIndex());
        }
      }
    });
    this.interaction.setActive(this.active);
    this.interaction.on('select', event => {
      this.selected = event.selected;
      this.selectedLisener(this.selected[0]);
    });
    this.map.addInteraction(this.interaction);
  }

  setMap(map: any): void {
    this.map = map;
  }

  addLayer(id: string, layer: CustomLayer): void {
    if(!this.layers[id]) {
      this.layers[id] = layer;
      this.interactionBuilder(this.serializeLayers(this.layers));
    }
  }

  removeLayer(id: string): void {
    if(this.layers[id]) {
      const { [id]: removed, ...layers} = this.layers;
      this.layers = layers;
      if(this.selectedFeature && this.selectedFeature.layerId === id) {
        this.selected = [];
        this.selectedLisener(this.selected[0]);
      }
      this.interactionBuilder(this.serializeLayers(this.layers));
    }
  }

  clearLayer(): void {
    this.layers = {};
    this.selected = [];
    this.selectedLisener(this.selected[0]);
    this.interactionBuilder(this.serializeLayers(this.layers));
  }

  private selectedLisener(selected: any): void {
    this.selectedFeature = this.formatFeature(selected);
    this.store.dispatch(new fromFeatureAction.FeatureSelected(this.selectedFeature));
  }

  private formatFeature(feature: any): Feature {
    if(feature) {
      const layer: CustomLayer = this.getLayer(feature);
      let id: string = feature.getId();
      if(layer.type === LayerTypes.WFS) {
        const options: WFSOptions = <WFSOptions>layer.options;
        const reg = new RegExp(`^${options.name}.`);
        id = id.replace(reg, '');
      }
      return {
        id: id,
        layerId: layer.id,
        layerName: layer.title,
        photoExist: layer.photoExist,
        properties: this.getProperties(feature)
      }
    } else {
      return null;
    }
  }

  private getLayer(feature: any): CustomLayer {
    const layer = this.interaction.getLayer(feature);
    const layerId = Object.keys(this.layers).find(key => layer === this.layers[key].getLayer());
    return this.layers[layerId];
  }

  private getProperties(feature: any): { [key: string]: any } {
    const { FEATURE_ID, LAYER_ID, geometry, bbox, ...properties } = feature.getProperties();
    return properties;
  }

  private stylesBuilder(): any {
    const white = [255, 255, 255, 1];
    const blue = [0, 153, 255, 1];
    const width = 3;
    this.styles['Polygon'] = [
      new Style({
        fill: new StyleFill({
          color: [255, 255, 255, 0.5]
        })
      })
    ];
    this.styles['MultiPolygon'] = this.styles['Polygon'];
    this.styles['LineString'] = [
      new Style({
        stroke: new StyleStroke({
          color: white,
          width: width + 2
        })
      }),
      new Style({
        stroke: new StyleStroke({
          color: blue,
          width: width
        })
      })
    ];
    this.styles['MultiLineString'] = this.styles['LineString'];
    this.styles['Point'] = [
      new Style({
        image: new StyleCircle({
          radius: width * 2,
          fill: new StyleFill({
            color: blue
          }),
          stroke: new StyleStroke({
            color: white,
            width: width / 2
          })
        }),
        zIndex: Infinity
      })
    ];
    this.styles['MultiPoint'] = this.styles['Point'];
    this.styles['GeometryCollection'] = this.styles['Polygon'].concat(this.styles['Point'] );
  }

  private getStyle(feature: any, colorIndex: number): any {
    const styles = new DefaultStyle(colorIndex, true);
    const geometry = feature.getGeometry();
    const type = geometry.getType();
    return styles.style(type);
  }

  private serializeLayers(layers: { [key: string]: CustomLayer }): any[] {
    return Object.keys(layers).map(key => layers[key].getLayer());
  }

}
